package openglsuperbible.Chapter7;

import de.matthiasmann.twl.utils.PNGDecoder;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import openglsuperbible.glutils.*;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL32.*;
/**
 *
 * @author Matthew
 */
public class PointSprites {
        public static final int DISPLAY_HEIGHT = 900;
        public static final int DISPLAY_WIDTH = 1440;

        public long lastFrame;       
        private Shader starFieldShader;
 
        // Shader
        
        private MatrixStack modelViewMatrix;  // Modelview Matrix
        private MatrixStack projectionMatrix; // Projection Matrix
        private GLFrustrum frustrum;    // View Frustum
        private GeometryTransform transformPipeline; // Geometry Transform Pipeline
 
        private GLBatch starsBatch;
        private int texture;
        private int NUM_STARS = 10000;
        float fColors[][] = {{ 1.0f, 1.0f, 1.0f, 1.0f}, // White
                             { 0.67f, 0.68f, 0.82f, 1.0f}, // Blue Stars
                             { 1.0f, 0.5f, 0.5f, 1.0f}, // Reddish
	                     { 1.0f, 0.82f, 0.65f, 1.0f}}; // Orange
        
        
        
        public void initGL()
        {
           
            // Black background
            glClearColor(0.0f, 0.0f, 0.0f, 1.0f );
            glEnable(GL_POINT_SPRITE);
            starFieldShader = new Shader("SpaceFlight","SpaceFlight");
         
            frustrum = new GLFrustrum();               
            modelViewMatrix = new MatrixStack();
     
            texture = loadTexture("star",GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
           glBindTexture(GL_TEXTURE_2D, texture);

            
            GLBatchFactory batch = new GLBatchFactory();
              // Randomly place the stars in their initial positions, and pick a random color
   
         float[] vColor = new float[4];
         float[] vPosition = new float[3];
        for(int i = 0; i < NUM_STARS; i++)
        {
            
		int iColor = 0;		
                // All stars start as white
                
                
		// One in five will be blue
                if(rand(100) % 5 == 1)
			iColor = 1;
                
		// One in 50 red
		if(rand(100) % 50 == 1)
			iColor = 2;

		// One in 100 is amber
		if(rand(100) % 100 == 1)
			iColor = 3;

                
                vColor[0] = fColors[iColor][0];
                vColor[1] = fColors[iColor][1];
                vColor[2] = fColors[iColor][2];
                vColor[3] = fColors[iColor][3];
      
                
                vPosition[0] = (float)(3000 - (rand() % 6000)) * 0.1f;
                vPosition[1] = (float)(3000 - (rand() % 6000)) * 0.1f;
                vPosition[2] = -(float)(rand() % 1000)-1.0f;   // -1 to -1000.0f
//                System.out.println("X:"+ vPosition[0]+ ",Y:"+ vPosition[1]+",Z:"+vPosition[2]);
                batch.addVertex(vPosition,vColor,null,null);
     
          }
             
            starsBatch = batch.buildBatch(GL_POINTS);

        }

  
    void render()
    {    
   
    
	// Clear the window with current clearing color
	 glClear(GL_COLOR_BUFFER_BIT);
         
         // Turn on additive blending
            glEnable(GL_BLEND);
            glBlendFunc(GL_ONE, GL_ONE);
            
       // Let the vertex program determine the point size
	 glEnable(GL_PROGRAM_POINT_SIZE);
  
         
        starFieldShader.use(); 
         
        starFieldShader.setUniformMatrix4("mvpMatrix", transformPipeline.getModelViewProjectionMatrix());
        starFieldShader.setUniform1i("starImage", 0);
        
 
        starFieldShader.setUniform1f("timeStamp", fTime());
        
        starsBatch.draw(starFieldShader.getAttributeLocations());
        starFieldShader.endUse();

    
    
	// Flush drawing commands
	Display.update();  
    }
    
    
        
       public void create() throws LWJGLException { 
            //Display    
            Display.setDisplayModeAndFullscreen(new DisplayMode(DISPLAY_WIDTH,DISPLAY_HEIGHT)); 
            
            Display.setTitle("Spaced Out");
            Display.create();
            
            //Keyboard         
            Keyboard.create();   
            //Mouse          
            Mouse.setGrabbed(false);
            Mouse.create();  
            //OpenGL      
            initGL();    
            resizeGL();  
        }  
       
        public void resizeGL() {   
            glViewport(0,0,DISPLAY_WIDTH ,DISPLAY_HEIGHT);
            frustrum.setPerspective(35f, Display.getWidth()/ Display.getHeight(),1.0f, 1000.0f);
            projectionMatrix = new MatrixStack(frustrum.getProjectionMatrix());
            // Set the transformation pipeline to use the two matrix stacks 
            transformPipeline = new GeometryTransform(modelViewMatrix, projectionMatrix);
        } 
       
            public void destroy() {  
                //Methods already check if created before destroying.  
                Mouse.destroy();   
                Keyboard.destroy();
                Display.destroy();     
            } 
        
            public static void main(String[] args) {
            PointSprites main = null;
            try { 
                 main = new PointSprites(); 
                main.create(); 
                main.run();
            }          
            catch(Exception ex) {  
                 
            }          
            finally { 
                if(main != null) {  
                    main.destroy();
                }         
            }      
        } 
            
            public void run() 
            {
                while (!Display.isCloseRequested() && !Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {              
                       
                render();
       
             
                Display.sync(60);
                Display.setVSyncEnabled(true); 
                
                }
            }
                        
                            	/** 
	 * Calculate how many milliseconds have passed 
	 * since last frame.
	 * 
	 * @return milliseconds passed since last frame 
	 */
	public int getDelta() {
	    long time = getTime();
	    int delta = (int) (time - lastFrame);
	    lastFrame = time;
	 
	    return delta;
	}
	
	/**
	 * Get the accurate system time
	 * 
	 * @return The system time in milliseconds
	 */
	public long getTime() {
	    return (Sys.getTime() * 1000) / Sys.getTimerResolution();
	}
        
        /**
         * goes from 0.0 to 999.0 and recycles
         * @return 
         */
        public float fTime()
        {
            return fmod(getTime() /10.0f, 999.0f);
        }


    /**
    * Returns the floating point remainder
    * @param numerator
    * @param denominator
    * @return 
    */
    private float fmod(float numerator, float denominator) {
        return numerator % denominator;
    }
        
            
    public int rand()
    {
        return rand(30000);
    }

    public int rand(int outOf)
    {

        return (int)(Math.random()* outOf);
    }
    
    
    private int loadTexture(String filename, int minFilter, int magFilter, int wrapMode){
            
            InputStream in;
            int texID = glGenTextures();
            try {
                in = new FileInputStream("textures/" + filename + ".png");
                PNGDecoder decoder = new PNGDecoder(in);
                int width = decoder.getWidth();
                int height = decoder.getHeight();
                
                ByteBuffer buffer = BufferUtils.createByteBuffer(4 * width * height);
                decoder.decode(buffer, width * 4, PNGDecoder.Format.RGBA);          
                buffer.flip();
                in.close();
                
                glBindTexture(GL_TEXTURE_2D, texID);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
                
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
                
                glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
                glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

//                             
                if(minFilter == GL_LINEAR_MIPMAP_LINEAR || 
                    minFilter == GL_LINEAR_MIPMAP_NEAREST ||
                     minFilter == GL_NEAREST_MIPMAP_LINEAR ||
                     minFilter == GL_NEAREST_MIPMAP_NEAREST){
                glGenerateMipmap(GL_TEXTURE_2D);}       
                glBindTexture(GL_TEXTURE_2D, 0);
                
            } catch (FileNotFoundException e) {
                System.err.println("Failed to find the texture file: " + filename + ".png");
                
            } catch (IOException ex) {
                System.err.println("Error reading file: " + filename + ".png");
                
            } 
           
            return texID;
        }
}
